package com.joe.springboot.config;

import com.alibaba.fastjson.JSON;
import com.joe.springboot.po.response.ErrorResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

@Order(1)
@ControllerAdvice
public class UnifiedExceptionHandler extends RequestBodyAdviceAdapter {
    private static final Logger log = LoggerFactory.getLogger(UnifiedExceptionHandler.class);

    private SimpleRequestBody requestBody;

//    @InitBinder
//    public void initBinder(WebDataBinder binder) {
//
//        log.info("============应用到所有@RequestMapping注解方法，在其执行之前初始化数据绑定器, {}", binder.getFieldDefaultPrefix());
//    }

    /**
     * 参数绑定异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler({BindException.class})
    @ResponseBody
    public ErrorResponse handleBindException(HttpServletRequest req, BindException e) {
        printLog(req, e);
        return wrapperBindingResult(e.getBindingResult());
    }

    @ExceptionHandler({ConstraintViolationException.class})
    @ResponseBody
    public ErrorResponse handleBindException(HttpServletRequest req, ConstraintViolationException e) {
        printLog(req, e);
        return new ErrorResponse(50001, e.getLocalizedMessage());
    }

    /**
     * 参数校验异常，将校验失败的所有异常组合成一条错误信息
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public ErrorResponse handleValidException(HttpServletRequest req, MethodArgumentNotValidException e) {
        printLog(req, e);
        return wrapperBindingResult(e.getBindingResult());
    }

    /**
     * 包装绑定异常结果
     *
     * @param bindingResult 绑定结果
     * @return 异常结果
     */
    private ErrorResponse wrapperBindingResult(BindingResult bindingResult) {
        StringBuilder msg = new StringBuilder();

        for (ObjectError error : bindingResult.getAllErrors()) {
            msg.append(", ");
            if (error instanceof FieldError) {
                msg.append(((FieldError) error).getField()).append(": ");
            }
            msg.append(error.getDefaultMessage() == null ? "" : error.getDefaultMessage());

        }

        return new ErrorResponse(50002, msg.substring(2));
    }

    /**
     * 未定义异常
     *
     * @param e 异常
     * @return 异常结果
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ErrorResponse handleException(HttpServletRequest req, Exception e) {
        printLog(req, e);
        return new ErrorResponse(60003, e.getMessage());
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        return inputMessage;
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        this.setRequestBody(new SimpleRequestBody(parameter, body));
        return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
    }


    private void printLog(HttpServletRequest req, Exception e) {
        SimpleRequestBody requestBody = this.getRequestBody();
        if (requestBody == null) {
            return;
        }
        MethodParameter parameter = requestBody.getParameter();
        Method method = parameter.getMethod();
        String s = JSON.toJSONString(requestBody.getBody());
        log.error("异常请求URL:{}, method:{}, req.json= {}", req.getPathInfo(), method, s, e);

    }

    public SimpleRequestBody getRequestBody() {
        return this.requestBody;
    }

    public void setRequestBody(SimpleRequestBody requestBody) {
        this.requestBody = requestBody;
    }


    private class SimpleRequestBody {

        private MethodParameter parameter;

        private Object body;

        public SimpleRequestBody(MethodParameter parameter, Object body) {
            this.parameter = parameter;
            this.body = body;
        }

        public MethodParameter getParameter() {
            return this.parameter;
        }

        public void setParameter(MethodParameter parameter) {
            this.parameter = parameter;
        }

        public Object getBody() {
            return this.body;
        }

        public void setBody(Object body) {
            this.body = body;
        }
    }

}